'use strict';

const uidObj = require('uni-id');
const uniSecObj = require('uni-sec-check');

const uniConfig = require('uni-config-center');
const uidConfig = uniConfig({
	pluginId: 'uni-id'
});

const {
	Controller
} = require('uni-cloud-router');

module.exports = class MemberController extends Controller {

	// 注册 
	async register() {
		let response = {
			code: 1,
			msg: '账号注册失败',
			datas: {}
		};
		const {
			sharemid,
			username,
			password,
			code,
			user
		} = this.ctx.data;

		// 验证手机号是否有效
		if (!/(^1[0-9][0-9]{9}$)/.test(username)) {
			response.msg = '请输入正确的手机号';
			return response;
		}
		
		// 验证验证码有效性
		const vcRes = await this.validateSmsCode({
			mobile: username,
			code: code,
			type: "register"
		});
		//  && code !== '123456'
		if (!(uidConfig.config('env') === 'dev' && code === '123456')) {
			if (vcRes.data.length !== 1) {
				response.datas = vcRes;
				response.msg = '验证码错误或已失效';
				return response;
			}
		}
		
		// 配置项
		const config = this.service.utils.getConfig();
		// 用户昵称
		const nickname = `${config.appabbr}${this.service.utils.getRCode(4)}`;
		
		const res = await uidObj.register({
			nickname,
			username,
			password,
			mobile: username,
			mobile_confirmed: 1,
			role: ['member']
		});

		if (res.code !== 0) {
			response.datas = res;
			response.msg = res.message;
			return response;
		}

		let citys = [user.country, user.province, user.city];

		let member = {
			member_password: res.password || '',
			member_mobile: res.username,
			member_access_token: res.token,
			member_nickname: nickname,
			member_gender: user.gender || 0,
			member_headimg: user.avatarUrl || config.headimg,
			member_weixin_headimg: user.avatarUrl || '',
			member_city: citys.filter(x => x).join('-') || '',
		}

		if (vcRes.data && vcRes.data.length) {
			const vcid = vcRes.data[0]._id;
			// 验证码已验证
			await this.db.collection('opendb-verify-codes').doc(vcid).update({
				state: 1
			});
		}

		response.datas.user = res;
		response.datas.member = await this.memberLogin(res, member, sharemid);

		response.code = 0;
		response.msg = '注册成功';

		return response;
	}

	// 登录
	async login() {
		let response = {
			code: 1,
			member: {},
			user: {},
			msg: null
		};

		const {
			sharemid,
			username,
			password
		} = this.ctx.data

		try {
			const res = await uidObj.login({
				username,
				password,
				needPermission: true
			})
			console.log(res);

			if (!(res && res.uid)) {
				response.msg = '账号密码不存在';
				return response;
			}

			let member = {
				member_access_token: res.token,
				member_password: res.password || '',
				member_mobile: res.username,
			}

			response.code = 0;
			response.member = await this.memberLogin(res, member, sharemid);
			response.user = res;
		} catch (e) {
			response.msg = e.message;
			console.log('catch', e.message);
		}

		return response;
	}

	// 注销
	async logout() {
		const res = await uidObj.logout(this.ctx.event.uniIdToken);
		return res;
	}

	// 验证码
	async sendSmsCode() {
		const {
			mobile,
			type
		} = this.ctx.data;

		// 如果验证码类型为 注册，验证手机账号是否已存在
		// 如果验证码类型为 忘记密码，验证手机账号是否存在
		let userRes = {};
		switch (type) {
			case 'register': // 注册
				userRes = await this.db.collection('uni-id-users')
					.where({
						username: mobile,
						role: 'member'
					})
					.field({
						username: true
					})
					.get();
				// console.log('userRes', userRes);
				if (userRes && userRes.data.length === 1) {
					return {
						code: 1,
						msg: '手机号已存在'
					};
				}
				break;
			case 'forgot-password': // 忘记密码
				userRes = await this.db.collection('uni-id-users')
					.where({
						username: mobile,
						role: 'member'
					})
					.field({
						username: true
					})
					.get();
				// console.log('userRes', userRes);
				if (!(userRes && userRes.data.length === 1)) {
					return {
						code: 1,
						msg: '手机号不存在'
					};
				}
				break;
			default:
				break;
		}

		const res = await uidObj.sendSmsCode({
			...this.ctx.data,
			code: (Math.floor(Math.random() * 1000000) + "").padStart(6, '0') + "",
			templateId: uidConfig.config('service.smscode.templateId')
		});

		return res;
	}

	// 手机号+验证码 登录|注册
	async loginBySms() {
		let response = {
			code: 1,
			member: {},
			user: {},
			msg: null
		};

		const {
			sharemid,
			code, // 验证码
			mobile, // 手机号
			wxcode, // 授权登录 code
			user, // 授权登录用户数据
			type, // 类型  'unbind-bind':微信解绑并绑定
			from = 'mp' // mp小程序 app应用
		} = this.ctx.data;

		// 验证手机号是否有效
		if (!/(^1[0-9][0-9]{9}$)/.test(mobile)) {
			response.msg = '请输入正确的手机号';
			return response;
		}

		const res = await uidObj.loginBySms({
			mobile,
			code,
			role: ['member']
		});

		uniCloud.logger.info('loginBySms1', res);
		response.msg = res.message;

		if (res.code == 0) {
			// 配置项
			const config = this.service.utils.getConfig();
			// 昵称
			const nickname = `${config.appabbr}${this.service.utils.getRCode(4)}`;

			let member = {};
			// 绑定微信
			let weixin = {};
			if (wxcode && (!res.userInfo.wx_openid ||
					(from == 'mp' && !res.userInfo.wx_openid['mp-weixin']) ||
					(from == 'app' && !res.userInfo.wx_openid['app-plus']))) {

				const wxres = await uidObj.bindWeixin({
					uid: res.uid,
					code: wxcode
				});

				uniCloud.logger.info('wxres', wxres);

				if (wxres.code !== 0) {
					wxres.msg = this.formatCodeMsg(wxres);
					response.code = 100;
					response.datas = wxres;
					response.msg = wxres.msg;
					// await this.db.collection('uni-id-users').doc(res.uid).remove();
					return response;
				}

				if (from == 'app') {
					weixin.app = wxres.openid;
				}
				if (from == 'mp') {
					weixin.mp = wxres.openid;
					weixin.nickname = user.nickname;
				}
				weixin.uuid = wxres.unionid;

				member = {
					member_nickname: nickname,
					member_mobile: mobile,
					member_gender: user.gender || 0,
					member_headimg: user.avatar || config.headimg,
					member_city: user.comment || '',
					member_weixin: weixin
				}

				await this.db.collection('uni-id-users').doc(res.uid).update({
					nickname: nickname,
					gender: user.gender || 0,
					mobile_confirmed: 1,
					avatar: user.avatar || config.headimg
				})
			} else {
				const userRes = await this.db.collection('uni-id-users').doc(res.uid).field({
					nickname: true,
					avatar: true
				}).get();

				const [userObj] = userRes.data;
				if (userObj && !userObj.nickname && !userObj.avatar) {
					member = {
						member_nickname: nickname,
						member_mobile: mobile,
						member_headimg: config.headimg,
						member_gender: 0
					}

					await this.db.collection('uni-id-users').doc(res.uid).update({
						nickname: nickname,
						gender: 0,
						mobile_confirmed: 1,
						avatar: config.headimg
					})
				}
			}

			let memberRes = await this.memberLogin(res, member, sharemid);

			response.code = 0;
			response.member = memberRes;
			response.user = res;
		}

		return response;
	}

	// 微信小程序手机号 登录|注册
	async loginByPhoneNumber() {
		let response = {
			code: 1,
			member: {},
			user: {},
			msg: null
		};

		const {
			sharemid,
			encryptedData,
			iv,
			wxcode,
			user, // 授权登录用户数据
			type, // 类型  'unbind-bind':微信解绑并绑定
			from = 'mp' // mp小程序 app应用
		} = this.ctx.data;

		// 验证 uid 是否有效
		if (!(user && user.uid)) {
			response.msg = '需操作微信授权登录';
			return response;
		}

		// 解密手机号
		const bizDataRes = await uidObj.wxBizDataCrypt({
			code: wxcode,
			encryptedData,
			iv
		});
		uniCloud.logger.info('loginByPhoneNumber bizDataRes', bizDataRes);
		if (bizDataRes.code !== 0) {
			response.msg = bizDataRes.msg;
			response.datas = bizDataRes;
			return response;
		}

		const mobile = bizDataRes.phoneNumber;

		// 绑定手机号
		const bindRes = await uidObj.bindMobile({
			uid: user.uid,
			mobile: mobile
		});
		uniCloud.logger.info('loginByPhoneNumber bindMobile', bindRes);

		if (bindRes.code !== 0) {
			if (bindRes.code == 60101) {
				// 手机号已被绑定
				// 判断当前手机号是否已绑定微信账号，若未绑定 则进入手机号绑定微信流程
				response = await this.bindWeixinByMobile(mobile, user.uid);
				return response;
			}
			
			response.code = bindRes.code;
			response.msg = this.formatCodeMsg(bindRes);
			response.datas = bizDataRes.phoneNumber;
			return response;
		}

		let member = {
			member_mobile: mobile,
		}

		// 获取当前绑定者数据
		const userObjRes = await this.db.collection('uni-id-users').doc(user.uid).get();
		const [userObj] = userObjRes.data;
		if (!(userObj && userObj._id)) {
			response.msg = '需重新操作微信授权登录';
			return response;
		}
		// 获取最新 token 令牌
		const [userToken] = userObj.token.reverse();

		// 数据响应码
		user.code = 0;
		// 绑定最新 token
		const {
			token,
			tokenExpired
		} = await uidObj.refreshToken({
			token: userToken
		});
		user.token = token;
		user.tokenExpired = tokenExpired;
		user.mobile = mobile;
		
		member.member_weixin = {
			uuid: userObj.wx_unionid || '',
			mp: '',
			app: '',
			h5: ''
		};
		
		if (userObj.wx_openid)  {
			if (userObj.wx_openid['app-plus']) member.member_weixin.app = userObj.wx_openid['app-plus'];
			if (userObj.wx_openid['mp-weixin']) member.member_weixin.mp = userObj.wx_openid['mp-weixin'];
			if (userObj.wx_openid['h5-weixin']) member.member_weixin.h5 = userObj.wx_openid['h5-weixin'];
		}
		
		let memberRes = await this.memberLogin(user, member, sharemid);

		response.code = 0;
		response.member = memberRes;
		response.user = user;

		return response;
	}
	// 绑定微信 - 通过微信手机号
	async bindWeixinByMobile(mobile, uid) {
		// 判断手机号是否已绑定微信账号
		const mobileUserRes = await this.db.collection('usemall-member').where({
			member_mobile: mobile
		}).field({
			member_weixin: true
		}).get();
		
		const [mobileUser] = mobileUserRes.data;
		
		if (!(mobileUser && mobileUser._id)) {
			return {
				code: 103,
				datas: {},
				msg: '手机号不存在'
			}
		}
		
		const weixin = mobileUser.member_weixin;
		if (weixin && (weixin.app || weixin.mp || weixin.h5)) {
			return {
				code: 104,
				datas: {},
				msg: '手机号已存在并已绑定其他微信账号'
			}
		}
		
		// 查询 uid 数据
		const uidUserRes = await this.db.collection('uni-id-users').doc(uid).field({
			wx_unionid: true,
			wx_openid: true
		}).get();
		
		const [uidUser] = uidUserRes.data;
		
		// 删除 uid 数据
		await this.db.collection('uni-id-users').doc(uid).remove();
		await this.db.collection('usemall-member').doc(uid).remove();
		
		// 绑定微信账号
		const memberUpdateObj = {
			member_weixin: {
				uuid: uidUser.wx_unionid || '',
				mp: '',
				app: '',
				h5: ''
			}
		};
		const userUpdateObj = {
			wx_unionid: uidUser.wx_unionid,
			wx_openid: uidUser.wx_openid
		}
		
		if (uidUser.wx_openid)  {
			if (uidUser.wx_openid['app-plus']) memberUpdateObj.member_weixin.app = uidUser.wx_openid['app-plus'];
			if (uidUser.wx_openid['mp-weixin']) memberUpdateObj.member_weixin.mp = uidUser.wx_openid['mp-weixin'];
			if (uidUser.wx_openid['h5-weixin']) memberUpdateObj.member_weixin.h5 = uidUser.wx_openid['h5-weixin'];
		}
		
		await this.db.collection('uni-id-users').doc(mobileUser._id).update(userUpdateObj);
		await this.db.collection('usemall-member').doc(mobileUser._id).update(memberUpdateObj);
		
		
		// 获取当前绑定者数据
		const userObjRes = await this.db.collection('uni-id-users').doc(mobileUser._id).field({
			token: true
		}).get();
		const [userObj] = userObjRes.data;
		if (!(userObj && userObj._id)) {
			return {
				code: 103,
				datas: {},
				msg: '手机号不存在'
			}
		}
		// 获取最新 token 令牌
		const [userToken] = userObj.token.reverse();
		
		// 返回的用户对象
		const user = {};
		// 数据响应码
		user.code = 0;
		// 绑定最新 token
		const {
			token,
			tokenExpired
		} = await uidObj.refreshToken({
			token: userToken
		});
		user.token = token;
		user.tokenExpired = tokenExpired;
		user.mobile = mobile;
		
		// 用户ID
		user.uid = mobileUser._id;
		let memberRes = await this.memberLogin(user, { });
		
		return {
			code: 0,
			member: memberRes,
			user: user,
			msg: ''
		}
	}

	// 微信小程序登录
	async loginByWeixin() {
		let response = {
			code: 1,
			member: {},
			user: {},
			msg: null
		};
		try {
			const {
				sharemid,
				code,
				user
			} = this.ctx.data;

			const res = await uidObj.loginByWeixin({
				code,
				role: ['member']
			});
			// console.log('loginByWeixin res', res);
			if (res.code !== 0) {
				response.msg = res.msg;
				return response;
			}

			let member = {
				member_access_token: res.token,
				member_session_key: res.sessionKey || ''
			}

			let memberRes = await this.memberLogin(res, member, sharemid);

			if (res.type == 'register') {
				// 配置项
				const config = this.service.utils.getConfig();
				const nickname = `${config.appabbr}${this.service.utils.getRCode(4)}`;

				await this.db.collection('uni-id-users').doc(res.uid).update({
					nickname: nickname,
					avatar: config.headimg
				});
				const memberUpdObj = {
					member_nickname: nickname,
					member_headimg: config.headimg
				};
				await this.db.collection('usemall-member').doc(res.uid).update(memberUpdObj);

				memberRes = {
					...memberRes,
					...memberUpdObj
				};
			}

			// 注册成功，判断是否已绑定手机号
			if (!res.mobileConfirmed) {
				response.code = 100;
				response.msg = '请绑定手机号';
				response.datas = res.uid;

				return response;
			}
			
			const { PLATFORM } = this.ctx.context;
			const openid = res.openid;
			if (!(memberRes.member_weixin && memberRes.member_weixin.mp) && PLATFORM === 'mp-weixin') {
				await this.db.collection('usemall-member').doc(memberRes._id).update({
					member_weixin: {
						mp: openid
					}
				});
				memberRes.member_weixin.mp = openid;
			} else if (!(memberRes.member_weixin && memberRes.member_weixin.app)) {
				await this.db.collection('usemall-member').doc(memberRes._id).update({
					member_weixin: {
						app: openid
					}
				});
				memberRes.member_weixin.app = openid;
			}
			
			response.code = 0;
			response.member = memberRes;
			response.user = res;
		} catch (e) {
			response.msg = e.message;
			console.log('catch', e.message);
		}

		return response;
	}

	// 微信公众号 code 授权
	async h5WeixinAuth() {
		let response = {
			code: 1,
			datas: {},
			msg: null
		};
		try {
			const {
				code
			} = this.ctx.data;

			// 请求微信公众号获取用户openid接口
			const appid = uidConfig.config('h5-weixin.oauth.weixin.appid');
			const appsecret = uidConfig.config('h5-weixin.oauth.weixin.appsecret');
			const url =
				`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${appsecret}&code=${code}&grant_type=authorization_code`;

			const res = await this.curl(url, {
				nestedQuerystring: true,
				method: 'get',
				dataType: 'json'
			});

			console.log('h5Weixin oauth res', res.data);
			if (res.data.errcode) {
				response.msg = res.data.errmsg;
				response.code = res.data.errcode;
			} else {
				// 根据用户 openid、access_token 获取用户数据，进行 注册|登录
				// 返回用户数据

				// 根据 openid 查询是否存在数据
				const openid = res.data.openid;
				// unionid 开放平台唯一ID
				let unionid = res.data.unionid || '';
				if (!unionid) {
					const h5WeixinUserRes = await this.getH5WeixinSnsUserinfo(res.data);
					unionid = h5WeixinUserRes.unionid;
				}
				
				const uniUserWhere = [];
				uniUserWhere.push({
					wx_openid: {
						'h5-weixin': openid
					}
				});
				if (unionid) {
					// 存在 unionid
					uniUserWhere.push({
						wx_unionid: unionid
					});
				}

				const uniUserRes = await this.db.collection('uni-id-users')
					.where(this.db.command.or(uniUserWhere))
					// 不查询敏感数据
					.field({
						password: false,
						password_secret_version: false,
						login_ip_limit: false
					})
					.get();

				console.log('uniUserRes', uniUserRes);
				// 判断是否已注册
				let member = {};
				let [uniUser] = uniUserRes.data;
				if (uniUser == null || !uniUser._id) {
					// 注册 h5Weixin 用户
					uniUser = await this.regH5WeixinUser(res.data);
					console.log('uniUserRes 注册 h5Weixin 用户', uniUser);

					member = {
						member_openid: openid,
						member_nickname: uniUser.nickname,
						member_gender: uniUser.gender,
						member_headimg: uniUser.avatar,
						member_weixin_headimg: uniUser.avatar,
						member_weixin: {
							h5: openid
						},
						member_city: uniUser.comment
					};
				} else {
					// 登录获取最新 token 令牌数据
					uniUser.uid = uniUser._id;
					
					let userRes;
					// 校验当前 token 是否已失效
					if (this.ctx.event.uniIdToken) {
						userRes = await uidObj.checkToken(this.ctx.event.uniIdToken);
					}
					if (!userRes || userRes.code !== 0) {
						// 失效则创建新的 token 并存储
						const tokenRes = await uidObj.createToken({
							uid: uniUser.uid,
							role: uniUser.role
						});

						const tokenList = uniUser.token || [];

						uniUser.token = tokenRes.token;
						uniUser.tokenExpired = tokenRes.tokenExpired;

						tokenList.push(uniUser.token);

						await this.db.collection('uni-id-users')
							.doc(uniUser.uid)
							.update({
								last_login_date: Date.now(),
								last_login_ip: this.ctx.context.CLIENTIP,
								token: tokenList
							});
					} else {
						// 有效则直接返回
						uniUser = userRes;
					}
				}

				// 调用登录公共模块
				let memberRes = await this.memberLogin(uniUser, member, '');

				// 同步更新 openid 数据
				if (!(uniUser.wx_openid && uniUser.wx_openid['h5-weixin'])) {
					await this.db.collection('uni-id-users').doc(uniUser.uid).update({
						wx_openid: {
							'h5-weixin': openid
						}
					});
				}
				if (!(memberRes.member_weixin && memberRes.member_weixin.h5)) {
					await this.db.collection('usemall-member').doc(memberRes._id).update({
						member_weixin: {
							h5: openid
						}
					});
					memberRes.member_weixin.h5 = openid;
				}

				response.member = memberRes;
				response.user = uniUser;

				response.datas = {
					openid: openid,
					expires_in: res.data.expires_in
				};
				response.code = 0;
				
				console.log('h5Weixin res', response);
			}

		} catch (e) {
			response.msg = e.message;
			console.log('catch', e.message);
		}

		return response;
	}
	// 获取用户基本信息
	async getH5WeixinSnsUserinfo(req) {
		const url =
			`https://api.weixin.qq.com/sns/userinfo?access_token=${req.access_token}&openid=${req.openid}&lang=zh_CN`;

		const res = await this.curl(url, {
			nestedQuerystring: true,
			method: 'get',
			dataType: 'json'
		});

		return res.data;
	}
	// 注册 h5Weixin 公众号用户数据
	async regH5WeixinUser(req) {

		const h5WeixinUserRes = await this.getH5WeixinSnsUserinfo(req);
		// 生成随机密码
		const password = Date.now() + (Math.round(Math.random() * 1000000) + '').padStart(6, '0');
		// 调用 uni-id 注册接口
		const res = await uidObj.register({
			username: req.openid,
			password: password,
			role: ['member'],
			wx_openid: {
				'h5-weixin': h5WeixinUserRes.openid
			},
			wx_unionid: h5WeixinUserRes.unionid,
			avatar: h5WeixinUserRes.headimgurl,
			gender: h5WeixinUserRes.sex,
			nickname: h5WeixinUserRes.nickname
		});

		res.nickname = h5WeixinUserRes.nickname;
		res.gender = h5WeixinUserRes.gender;
		res.avatar = h5WeixinUserRes.headimgurl;
		return res;
	}

	// 支付宝小程序登录
	async loginByAlipay() {
		let response = {
			code: 1,
			member: {},
			user: {},
			msg: null
		};
		try {
			const {
				sharemid,
				code
			} = this.ctx.data;
			const res = await uidObj.loginByAlipay({
				code,
				role: ['member']
			});

			uniCloud.logger.info(res);

			let member = {
				member_access_token: res.token,
				member_session_key: res.sessionKey || '',
				member_nickname: res.userInfo.nickname || res.username,
				member_gender: res.userInfo.gender || 0,
				member_headimg: res.userInfo.avatar || '',
				member_weixin_headimg: res.userInfo.avatar || '',
				member_city: res.userInfo.comment || '',
				member_openid: res.openid || '',
			}

			response.code = 0;
			response.member = await this.memberLogin(res, member, sharemid);
			response.user = res;

		} catch (e) {
			response.msg = e.message;
			console.log('catch', e.message);
		}

		return response;
	}
	
	// 解绑微信账号
	async unbindWeixin() {
		let response = {
			code: 1,
			datas: {},
			msg: null
		};
		
		let uid = '';
		if (this.ctx.event.uniIdToken) {
			// 已登录，获取当前登录 uid
			const userRes = await uidObj.checkToken(this.ctx.event.uniIdToken);
			if (userRes.code == 0) {
				uid = userRes.uid;
			}
		}
		if (!uid) {
			response.msg = '当前账号登录已失效，请重新登录';
			return response;
		}
		
		const memberRes = await this.db.collection('usemall-member').doc(uid).field({
			member_weixin: true,
			member_nickname: true,
			member_headimg: true
		}).get();
		
		const [memberObj] = memberRes.data;
		if (!(memberObj && memberObj._id)) {
			response.msg = '当前账号登录已失效，请重新登录';
			return response;
		}
		
		try{
			// 解绑微信
			let member = {
				member_weixin: {
					uuid: '',
					app: '',
					mp: '',
					h5: ''
				}
			};
			
			await this.db.collection('uni-id-users').doc(uid).update({
				wx_unionid: '',
				wx_openid: {
					'app-plus': '',
					'mp-weixin': '',
					'h5-weixin': ''
				}
			});
			await this.db.collection('usemall-member').doc(uid).update(member);
			
			response.code = 0;
			response.datas = member;
			response.msg = '解绑';
			response.uid = uid;
			
		} catch(e) {
			console.log('unbindWeixin err', e);
			response.msg = e.message;
			response.code = 500;
		}
		
		return response;
	}
	
	// 绑定微信账号
	async bindWeixin() {
		let response = {
			code: 1,
			member: {},
			user: {},
			msg: null
		};
	
		const {
			sharemid,
			wxcode,
			from,
			update
		} = this.ctx.data;
	
		if (!wxcode) {
			response.msg = '请授权微信登录';
			return response;
		}
	
		let uid = '';
		if (this.ctx.event.uniIdToken) {
			// 已登录，获取当前登录 uid
			const userRes = await uidObj.checkToken(this.ctx.event.uniIdToken);
			if (userRes.code == 0) {
				uid = userRes.uid;
			}
		}
		if (!uid) {
			response.msg = '当前账号登录已失效，请重新登录';
			return response;
		}
		
		const memberRes = await this.db.collection('usemall-member').doc(uid).field({
			member_weixin: true,
			member_nickname: true,
			member_headimg: true
		}).get();
	
		const [memberObj] = memberRes.data;
		if (!(memberObj && memberObj._id)) {
			response.msg = '当前账号登录已失效，请重新登录';
			return response;
		}
		
		let wxRes = {};
		if (memberObj.member_weixin && (memberObj.member_weixin.uuid || memberObj.member_weixin.app || memberObj.member_weixin.mp)) {
			wxRes = await uidObj.code2SessionWeixin({
				code: wxcode
			});
			
			if (wxRes.code !== 0) {
				wxRes.msg = this.formatCodeMsg(wxRes);
				response.datas = wxRes;
				response.msg = wxRes.msg;
				return response;
			}
			
			// 已绑定
			// 判断当前微信ID是否一致
			if (memberObj.member_weixin[from] == wxRes.openid) {
				response.msg = '已绑定成功';
				return response;
			}
			
			// 解绑
			response.code = 101;
			response.msg = '是否解绑并绑定当前微信';
			return response;
		}
		
		wxRes = await uidObj.bindWeixin({
			uid: uid,
			code: wxcode
		});
	
		if (wxRes.code !== 0) {
			wxRes.msg = this.formatCodeMsg(wxRes);
			response.datas = wxRes;
			response.msg = wxRes.msg;
			return response;
		}
	
		// 绑定微信
		let weixin = {};
		weixin.uuid = wxRes.unionid;
		weixin[from] = wxRes.openid;
	
		let member = {
			member_weixin: weixin
		};
	
		await this.db.collection('usemall-member').doc(uid).update(member);
	
		response.code = 0;
		response.datas = member;
		response.msg = '绑定成功';
	
		return response;
	}

	// 验证验证码有效性
	async validateSmsCode(param) {
		const vcRes = await this.db.collection('opendb-verify-codes').where({
			...param,
			state: 0,
			expired_at: this.db.command.gte(Date.now())
		}).get();

		console.log("vcRes: ", vcRes, param);
		return vcRes;
	}
	// 忘记密码
	async forgotPassword() {
		let response = {
			code: 1,
			msg: '账号不存在',
			datas: { }
		};

		const {
			mobile,
			password,
			code
		} = this.ctx.data;

		// 验证验证码有效性
		const vcRes = await this.validateSmsCode({
			mobile: mobile,
			code: code,
			type: "forgot-password"
		});
		if (vcRes.data.length !== 1) {
			response.datas = vcRes;
			response.msg = '验证码错误或已失效';
			return response;
		}

		// 加密密码
		const encryptPwd = await uidObj.encryptPwd(password);
		if (!(encryptPwd && encryptPwd.passwordHash)) {
			response.msg = '密码修改失败';
			return response;
		}

		const userRes = await this.db.collection('uni-id-users').where({
			mobile: mobile,
			role: 'member'
		}).get();
		const [user] = userRes.data;
		if (!(user && user._id)) {
			// 账号不存在
			return response;
		}
		
		if (user.password == encryptPwd.passwordHash && user.password_secret_version == encryptPwd.version) {
			// 密码相同情况下，不做修改
			response.datas.updated = 1;
		} else {
			// 修改密码
			response.datas = await this.db.collection('uni-id-users').doc(user._id)
				.update({
					username: mobile,
					password: encryptPwd.passwordHash,
					password_secret_version: encryptPwd.version
				});
		}

		if (response.datas.updated === 1) {
			const vcid = vcRes.data[0]._id;
			// 验证码已验证
			await this.db.collection('opendb-verify-codes').doc(vcid).update({
				state: 1
			});

			response.code = 0;
			response.msg = '密码修改成功';
			return response;
		}

		return response;
	}

	// 获取数据
	async data() {
		let response = {
			code: 1,
			member: {},
			stats: {}
		};

		let uid = '';
		if (this.ctx.event.uniIdToken) {
			// 已登录，获取当前登录 uid
			const userRes = await uidObj.checkToken(this.ctx.event.uniIdToken);
			if (userRes.code == 0) {
				uid = userRes.uid;
			}
		}
		if (!uid) {
			response.msg = '当前未登录'
			return response;
		}
		const $ = this.db.command.aggregate;
		// 我的订单
		const orderRes = await this.db.collection('usemall-order').aggregate()
			.match({
				create_uid: uid
			})
			.group({
				_id: '$state',
				num: $.sum(1)
			})
			.end();

		// 我的足迹
		const browsing = await this.db.collection('usemall-goods-history').where({
			create_uid: uid
		}).count();
		// 我的收藏
		const collect = await this.db.collection('usemall-member-collect').where({
			create_uid: uid,
			state: '已收藏'
		}).count();
		// 退款/退换货售后
		const aftersale = await this.db.collection('usemall-order-aftersale').where({
			is_delete: 0,
			create_uid: uid
		}).count();

		const memberRes = await this.db.collection('usemall-member').doc(uid)
			.field({
				member_session_key: false,
				member_password: false
			}).get();
		let memberData = {};
		if (memberRes && memberRes.data.length === 1) {
			memberData = memberRes.data[0];
			if (memberData.member_truename) {
				// 真实姓名脱敏处理
				memberData.member_truename = this.service.utils.desz(memberData.member_truename);
			}
		}
		response.member = memberData;
		// 开具发票
		response.invoice = uidConfig.config('invoice');
		
		response.stats = {
			order: orderRes.data,
			browsing: browsing.total,
			collect: collect.total,
			aftersale: aftersale.total
		}
		response.code = 0;

		return response;
	}

	// 修改用户指定数据
	async updateUser() {
		let response = {
			code: 1,
			datas: {}
		};

		const {
			token
		} = this.ctx.data;
		
		const user = await uidObj.checkToken(token || this.ctx.event.uniIdToken);
		response.msg = user.message;
		response.datas = user;

		if (user && user.code == 0) {
			const {
				uniUser,
				useUser
			} = this.getUpdateData(this.ctx.data);
			
			if (uniUser.nickname || uniUser.truename || uniUser.slogan) {
				const uCheck = new uniSecObj({
					provider: 'mp-weixin',
					requestId: this.ctx.context.requestId
				});
				
				// 昵称、真实姓名、slogan 文本内容安全检测
				const txtRes = await uCheck.textSecCheck({
					content: uniUser.nickname || uniUser.truename || uniUser.slogan
				});
				console.log('txtRes', txtRes);
				if (txtRes.errCode) {
					this.throw('您的文字存在违规内容');
				}
			}

			if (uniUser.avatar) {
				const uCheck = new uniSecObj({
					provider: 'mp-weixin',
					requestId: this.ctx.context.requestId
				});
				
				// 头像安全检测
				const imgRes = await uCheck.imgSecCheck({
					image: uniUser.avatar
				});
				console.log('imgRes', imgRes);
				if (imgRes.errCode) {
					this.throw('您的头像存在违规内容');
				}
			}

			await this.db.collection('uni-id-users').doc(user.uid).update(uniUser);
			response.datas = await this.db.collection('usemall-member').doc(user.uid).update(useUser);
			response.code = 0;
		}

		return response;
	}
	// 获取待修改的数据
	getUpdateData(user) {

		let uniUser = {};
		let useUser = {};
		let mapping = {
			'nickname': 'member_nickname',
			'truename': 'member_truename',
			'gender': 'member_gender',
			'avatar': 'member_headimg',
			'comment': 'member_city',
			'slogan': 'slogan',
			'is_perfect': 'is_perfect'
		}

		for (let key in user) {
			if (mapping[key]) {
				uniUser[key] = user[key];
				useUser[mapping[key]] = user[key];
			}
		}

		return {
			uniUser,
			useUser
		};
	}

	// 修改数据
	async update() {
		const user = await uidObj.checkToken(this.ctx.event.uniIdToken);
		if (user && user.code == 0) {
			const {
				nickname,
				gender,
				avatar,
				comment
			} = this.ctx.data;

			await this.db.collection('uni-id-users').doc(user.uid).update({
				nickname,
				gender,
				avatar,
				comment
			});
			await this.db.collection('usemall-member').doc(user.uid).update({
				member_nickname: nickname,
				member_gender: gender,
				member_headimg: avatar,
				member_weixin_headimg: avatar,
				member_city: comment
			});
		}

		return user;
	}

	// 收藏
	async collect() {
		const user = await uidObj.checkToken(this.ctx.event.uniIdToken);
		if (user && user.code == 0) {
			const {
				goods_id,
				state
			} = this.ctx.data;

			if (state == '已取消') {
				// 收藏数
				await this.db.collection('usemall-goods').where({
					_id: goods_id,
					collect_cnt: this.db.command.gt(0)
				}).update({
					collect_cnt: this.db.command.inc(-1)
				});
			} else {
				// 收藏数
				await this.db.collection('usemall-goods').doc(goods_id).update({
					collect_cnt: this.db.command.inc(1)
				});
			}


			let obj_id = user.uid + goods_id;
			return await this.db.collection('usemall-member-collect').doc(obj_id).set({
				goods: goods_id,
				state,
				create_uid: user.uid,
				create_time: Date.now()
			});
		}

		return user;
	}

	// 写入会员登录数据
	async memberLogin(res, member, sharemid) {
		// 
		let obj_id = res.uid;
		const nowdate = new Date().getTime();
		const utc8date = this.service.utils.toDateUTC8(Date.now());
		if (res.code == 0) {
			if (res.username && !res.mobile) {
				// 默认开启手机号登录
				await this.db.collection('uni-id-users').doc(res.uid).update({
					mobile: res.username,
					mobile_confirmed: 1
				});
			}
			if (res.mobile && !res.username) {
				// 默认开启手机号+密码登录
				await this.db.collection('uni-id-users').doc(res.uid).update({
					username: res.mobile
				});
			}

			const memberUpdateRes = await this.db.collection('usemall-member').doc(obj_id).update({
				...member,
				member_login_cnt: this.db.command.inc(1),
				member_login_current_ip: this.ctx.data.ip || this.ctx.context.CLIENTIP,

				version: this.db.command.inc(1),
				last_modify_uid: res.uid,
				last_modify_time: nowdate
			});

			if (memberUpdateRes && memberUpdateRes.updated <= 0) {
				await this.db.collection('usemall-member').doc(obj_id).set({
					...member,
					member_guid: obj_id,
					member_uuid: res.unionid,
					member_coupon_cnt: 0,
					member_amount: 0,
					member_monetary: 0,
					
					member_integral: 0,
					member_integral_sign_cnt: 0,
					member_integral_use: 0,
					member_integral_surplus: 0,
					member_integral_total: 0,
					
					member_login_cnt: 1,
					member_login_first_ip: this.ctx.data.ip || this.ctx.context.CLIENTIP,
					member_login_first_time: nowdate,
					
					profit_amount: 0,
					unprofit_amount: 0,
					member_economize: 0,
					member_weixin: {},
					
					is_plus: '会员',
					bind_has: 0,
					remark: '',
					version: 1,
					state: '已审核',
					yy_mm: this.service.utils.formatdate(utc8date, 'yyyy年MM月'),
					yy_mm_dd: this.service.utils.formatdate(utc8date, 'yyyy年MM月dd日'),
					create_uid: res.uid,
					create_time: nowdate
				});
				// 同步开通笔记功能
				await this.service.utils.openNoteUser(obj_id, {
					uname: member.member_nickname,
					uheadimg: member.member_headimg
				});
				
				// 注册赠送积分
				await this.service.integral.registerReward(obj_id, member.member_nickname);
				
				// 注册领取优惠券
				await this.service.coupon.registerReward(obj_id, member.member_nickname, member.member_headimg);
			} else {
				// 判断是否已开通笔记功能
				const noteUserRes = await this.db.collection('usemall-shopping-user').doc(obj_id).field({
					create_uid: true
				}).get();
				if (noteUserRes.data && !noteUserRes.data.length) {
					// 同步开通笔记功能
					await this.service.utils.openNoteUser(obj_id, {
						uname: member.member_nickname,
						uheadimg: member.member_headimg
					})
				}
			}

			const fxsRes = await this.db.collection('usemall-distributor').doc(obj_id).field({
				fxs_uid: true
			}).get();

			if (fxsRes.data && fxsRes.data.length <= 0) {
				// 写入分销商数据
				await this.db.collection('usemall-distributor').doc(obj_id).set({
					fxs_uid: obj_id,
					fxs_member_id: obj_id,
					fxs_member_openid: member.member_openid,
					fxs_profit_amount: 0,
					fxs_unprofit_amount: 0,
					fxs_cashout_amount: 0,
					fxs_unsettled_amount: 0,
					fxs_order_cnt: 0,
					fxs_member_cnt: 0,
					fxs_desc: '',
					remark: '',
					state: '启用',
					version: 1,
					is_delete: 0,
					create_time: nowdate,
					create_uid: obj_id,
					create_uname: member.member_nickname
				});

				await this.db.collection('usemall-distributor-log').add({
					fxs_uid: obj_id,
					oper_content: '注册|登录默认设为分销商',
					oper_type: '新增',
					oper_ip: this.ctx.data.ip || this.ctx.context.CLIENTIP,
					remark: '',
					version: 1,
					is_delete: 0,
					create_time: nowdate,
					create_uid: obj_id,
					create_uname: member.member_nickname
				});
			}
		}

		// 日志记录
		await this.uidlog(res);

		const memberRes = await this.db.collection('usemall-member').doc(res.uid)
			.field({
				member_session_key: false,
				member_password: false
			}).get();
		console.log('memberRes', memberRes);
		let memberData = {};
		if (memberRes && memberRes.data.length === 1) {
			memberData = memberRes.data[0];

			if (!memberData.myfxs_uid && !memberData.bind_has && sharemid && sharemid != obj_id) {
				// 修改登录者的 分销商
				console.log('修改登录者的 分销商', memberData, sharemid);

				// 获得 sharemid 的上级
				let sharemid2 = '';
				let shareData2 = {};

				const shareRes = await this.db.collection('usemall-member').doc(sharemid)
					.field({
						myfxs_uid: true
					}).get();
				const [shareUser] = shareRes.data;
				if (shareUser && shareUser._id) {
					if (shareUser.myfxs_uid) {
						// 上上级
						sharemid2 = shareUser.myfxs_uid
					}

					if (sharemid2) {
						shareData2 = {
							myfxs_uid2: sharemid2,
							myfxs_time2: nowdate,
							myfxs_source2: 'member',
							myfxs_order_cnt2: 0,
							myfxs_profit_amount2: 0,
						};
					}

					await this.db.collection('usemall-member').doc(obj_id).update({
						myfxs_uid: sharemid,
						myfxs_time: nowdate,
						myfxs_source: 'member',
						myfxs_order_cnt: 0,
						myfxs_profit_amount: 0,

						bind_has: 1,

						...shareData2
					});
					await this.db.collection('usemall-distributor').doc(sharemid).update({
						fxs_member_cnt: this.db.command.inc(1)
					});

					// 修改上级
					await this.db.collection('usemall-member').doc(sharemid).update({
						bind_has: 1
					});

					if (sharemid2) {
						await this.db.collection('usemall-distributor').doc(sharemid2).update({
							fxs_member_cnt: this.db.command.inc(1)
						});

						// 修改上上级
						await this.db.collection('usemall-member').doc(sharemid2).update({
							bind_has: 1
						});
					}

					memberData.myfxs_uid = sharemid;
					memberData.myfxs_uid2 = sharemid2;
				}
			}
			// 设置默认我的余额
			if (!memberData.member_amount) memberData.member_amount = 0;

			if (memberData.member_truename) {
				// 真实姓名脱敏处理
				memberData.member_truename = this.service.utils.desz(memberData.member_truename);
			}
		}

		return memberData;
	}

	// 日志记录
	async uidlog(res, type = 'login') {
		let data = {
			deviceId: this.ctx.data.deviceId || this.ctx.context.DEVICEID,
			ip: this.ctx.data.ip || this.ctx.context.CLIENTIP,
			type,
			ua: this.ctx.context.CLIENTUA,
			create_date: Date.now()
		};

		Object.assign(data, res.code === 0 ? {
			user_id: res.uid,
			state: 1
		} : {
			state: 0
		});

		return await this.db.collection('uni-id-log').add(data);
	}

	// 我的优惠券
	async coupon() {

		let response = {
			code: 1,
			datas: []
		};

		let start = new Date().getTime();

		let uid = '';
		if (this.ctx.event.uniIdToken) {
			// 已登录，获取当前登录 uid
			const user = await uidObj.checkToken(this.ctx.event.uniIdToken);
			if (user.code == 0) {
				uid = user.uid;
			}
		}
		if (!uid) {
			response.msg = '当前未登录'
			return response;
		}

		// 请求参数
		const req = this.ctx.data;

		let {
			page,
			rows,
			state
		} = req;

		const couponRes = await this.db.collection('usemall-marketing-coupon')
			.where({
				member_id: uid,
				state: state
			})
			.skip((req.page - 1) * req.rows)
			.limit(req.rows)
			.get();

		response.datas = couponRes;

		let end = new Date().getTime();
		console.log(`耗时：${end - start}ms`);
		response.code = 0;
		response.msg = `耗时：${end - start}ms`;
		return response;

	}

	// 我的发票
	async invoice() {
		const response = {
			code: 1,
			datas: []
		};

		let start = Date.now();

		let uid = '';
		if (this.ctx.event.uniIdToken) {
			// 已登录，获取当前登录 uid
			const user = await uidObj.checkToken(this.ctx.event.uniIdToken);
			if (user.code == 0) {
				uid = user.uid;
			}
		}
		if (!uid) {
			response.msg = '当前未登录'
			return response;
		}

		// 请求参数
		const req = this.ctx.data;

		let {
			page,
			rows,
			state
		} = req;

		const invoiceRes = await this.db.collection('usemall-order-invoice')
			.where({
				create_uid: uid,
				state: state
			})
			.orderBy('last_modify_time', 'desc')
			.skip((req.page - 1) * req.rows)
			.limit(req.rows)
			.get();

		response.datas = invoiceRes.data;

		let end = Date.now();
		console.log(`耗时：${end - start}ms`);
		response.code = 0;
		response.msg = `耗时：${end - start}ms`;
		return response;
	}
	
	// 申请开票
	async applyInvoice() {
		const response = {
			code: 1,
			datas: []
		};
		
		let start = Date.now();
		let nowdate = Date.now();
		
		let uid = '';
		if (this.ctx.event.uniIdToken) {
			// 已登录，获取当前登录 uid
			const user = await uidObj.checkToken(this.ctx.event.uniIdToken);
			if (user.code == 0) {
				uid = user.uid;
			}
		}
		if (!uid) {
			response.msg = '当前未登录'
			return response;
		}
		
		if (!uidConfig.config('invoice')) {
			response.msg = '当前未支持在线申请开票，请联系客服'
			return response;
		}
		
		// 请求参数
		const {
			order_id,
			invoice_id
		} = this.ctx.data;
		
		if (!order_id) this.throw('请选择开发票订单');
		if (!invoice_id) this.throw('请选择发票抬头');
		
		const orderRes = await this.db.collection('usemall-order')
			.where({
				order_id: order_id,
				create_uid: uid
			})
			.get();
		const [order] = orderRes.data;
		if (!(order && order._id)) {
			this.throw('请选择需开票的订单');
		}
		if (order.order_pay_state !== '已付款') {
			this.throw('订单未付款，不支持开票');
		}
		if (order.order_refund_state) {
			this.throw(`订单${order_refund_state}，不支持开票`);
		}
		if (![0, 3].includes(order.order_invoice_state)) {
			if (order.order_invoice_state === 1) this.throw(`开票中，请勿重复申请！`);
			if (order.order_invoice_state === 2) this.throw(`已开票成功，可进入开票中心查看`);
		}
		
		const invoiceRes = await this.db.collection('usemall-member-invoice')
			.doc(invoice_id)
			.field({
				
			})
			.get();
		const [invoiceData] = invoiceRes.data;
		if (!(invoiceData && invoiceData._id)) {
			this.throw('请选择正确的发票抬头');
		}
		if (invoiceData.create_uid != uid) {
			this.throw('发票抬头与当前账号不一致');
		}
		
		const res = await this.db.collection('usemall-order').doc(order._id).update({
			// 发票ID
			order_invoice_id: invoice_id,
			// 发票状态 0未申请 1申请中 2已开票 3已拒绝
			order_invoice_state: 1,
			// 发票信息
			order_invoice: invoiceData
		});
		if (res.updated) {
			// 开具发票
			await this.db.collection('usemall-order-invoice').doc(order.order_id).set({
				order_id: order.order_id,
				order_amount: order.order_actural_paid,
				invoice: invoiceData,
				invoice_url: '',
				state: 0,
				state_str: '申请中',
				version: 1,
				is_delete: 0,
				create_time: nowdate,
				create_uid: order.create_uid,
				create_uname: order.create_uname,
				last_modify_time: nowdate
			});
		}
		
		response.datas = res;

		let end = Date.now();
		console.log(`耗时：${end - start}ms`);
		response.code = 0;
		response.msg = `耗时：${end - start}ms`;
		return response;
	}
	
	// 当前零钱
	async money() {

		let response = {
			code: 1,
			msg: '',
			datas: {}
		};

		const uid = this.ctx.auth.uid;

		const memberRes = await this.db.collection('usemall-member').doc(uid)
			.field({
				member_amount: true
			})
			.get();
		let [memberData] = memberRes.data;

		response.datas = memberData;
		response.code = 0;
		return response;
	}

	// 反馈
	async feedback() {
		const response = {
			code: 1,
			datas: []
		};

		const start = Date.now();

		const {
			type_id,
			type_name,
			title,
			content,
			imgs,
			contact_way
		} = this.ctx.data;

		let uid = '';
		let uname = '';
		if (this.ctx.event.uniIdToken) {
			// 已登录，获取当前登录 uid
			const user = await uidObj.checkToken(this.ctx.event.uniIdToken);
			if (user.code == 0) {
				uid = user.uid;
				uname = user.userInfo.nickname || user.userInfo.username;
			}
		}
		if (!uid) {
			response.msg = '当前未登录'
			return response;
		}

		const feedbackRes = await this.db.collection('usemall-app-feedback')
			.add({
				type_id,
				type_name,
				title,
				content,
				imgs,
				contact_way,
				create_time: Date.now(),
				create_uid: uid,
				create_uname: uname
			});

		response.datas = feedbackRes;

		try {
			// 消息通知
			// 不需要 await，只需发送请求即可
			const messageNotifyUrl = `${uidConfig.config('message-notify.url')}/401`;
			if (messageNotifyUrl.indexOf('http') === 0) {
				await uniCloud.httpclient.request(messageNotifyUrl, {
					method: 'post',
					headers: {
						'message-notify-key': uidConfig.config('message-notify.key')
					},
					data: {
						id: feedbackRes.id,
						scene: 401,
						title: `反馈-${type_name}`,
						uname: `${uname} - ${contact_way}`,
						content: `${content}`
					},
					timeout: 30 * 1000,
					contentType: 'json',
					dataType: 'json'
				});
			}
		} catch (e) {
			console.log('message-notify err', e);
		}

		const end = Date.now();
		console.log(`耗时：${end - start}ms`);
		response.code = 0;
		response.msg = `耗时：${end - start}ms`;
		return response;
	}

	formatCodeMsg(res) {
		switch (res.code) {
			case 20101:
				return '手机号错误';
			case 20102:
				return '手机号已存在';
			case 30203:
			case 30204:
				return '请重新登录';
			case 60302:
				return '当前微信号已被绑定';
			case 60101:
				return '当前手机号已被绑定';
			default:
				return res.message || res.msg;
		}
	}
}
